package com.echat.elocation.connect;

import com.echat.elocation.event.ConnectionEvent;
import com.echat.elocation.util.HexStringUtils;
import com.echat.elocation.util.Logger;

import org.greenrobot.eventbus.EventBus;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * @author lihao
 * @date 2018/7/11 9:47
 */
public class NettyClient {

    private static NettyClient instance = new NettyClient();

    private static ExecutorService singleThreadExecutor;

    private EventLoopGroup group;
    private Channel mChannel;

    private boolean isConnect = false;
    private int reconnectNum = 10;
    private long reconnectIntervalTime = 5000;

    private static final String TAG = "NettyClient";

    public static NettyClient getInstance() {
        singleThreadExecutor = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>());
        return instance;
    }

    public void connect(final String host, final int port) {
        if (!isConnect()) {
            isConnect = true;
            singleThreadExecutor.execute(() -> syncConnect(host, port));
        }
    }

    private void syncConnect(String host, int port) {
        Logger.i(TAG, "host" + host + " : " + port);
        group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group);
        b.channel(NioSocketChannel.class);
        b.option(ChannelOption.TCP_NODELAY, true);
        b.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                p.addLast(new IdleStateHandler(0, 30, 0));
                p.addLast(new NettyClientHandler());
            }
        });
        try {
            ChannelFuture future = b.connect(host, port).sync();
            if (future != null && future.isSuccess()) {
                mChannel = future.channel();
            } else {
                Logger.w(TAG, "connect fail");
                isConnect = false;
                EventBus.getDefault().post(new ConnectionEvent(ConnectionEvent.CONNECT_FAIL));
            }
        } catch (Exception e) {
            Logger.d(TAG, "connect fail");
            isConnect = false;
            EventBus.getDefault().post(new ConnectionEvent(ConnectionEvent.CONNECT_FAIL));
            reconnect(host, port);
        }
    }

    public void disconnect() {
        isConnect = false;
        if (group != null) {
            group.shutdownGracefully();
        }
        if(mChannel != null){
            mChannel.closeFuture();
            mChannel.close();
        }
    }

    public void close(){
        disconnect();
        if (singleThreadExecutor != null) {
            singleThreadExecutor.shutdown();
        }
    }

    public void reconnect(String host, int port) {
        if (reconnectNum > 0 && !isConnect) {
            reconnectNum--;
            try {
                Thread.sleep(reconnectIntervalTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            disconnect();
            connect(host, port);
        } else {
            disconnect();
        }
    }

    public void sendMessage(byte[] data) {
        if (isConnect) {
            Logger.i(TAG, "Send message:" + HexStringUtils.toHexString(data).replaceAll("(.{2})", "$1 "));
            ByteBuf buf = Unpooled.copiedBuffer(data);
            mChannel.writeAndFlush(buf);
        } else {
            Logger.w(TAG, "sendMessage:连接还未建立, 无法发送数据...");
        }
    }

    public Channel getChannel() {
        return mChannel;
    }

    public boolean isConnect() {
        return isConnect;
    }

    public void setReconnectNum(int reconnectNum) {
        this.reconnectNum = reconnectNum;
    }

    public void setReconnectIntervalTime(long reconnectIntervalTime) {
        this.reconnectIntervalTime = reconnectIntervalTime;
    }

    public void setConnect(boolean connect) {
        isConnect = connect;
    }
}
